﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Skugo.Shared;
using System.Windows.Markup;
using Xceed.Wpf.Toolkit;
using System.Reflection;

namespace Skugo.StandardPlugins
{

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class DefaultEditorForTypeAttribute : Attribute
    {
        public Type Type;
    }

    public class UnknownTypeEditor : ITypeEditor
    {
        private TextBlock Block = new TextBlock();

        public UnknownTypeEditor(Type forType)
        {
            this.Block.Foreground = Brushes.DarkGray;
        }

        public void SetValue(object value)
        {
            if (value != null)
            {
                this.Block.Text = value.ToString();
            }
            else
            {
                this.Block.Text = "(null)";
            }
        }

        public bool IsEditing
        {
            get { return false; }
        }

        public FrameworkElement Content
        {
            get { return this.Block; }
        }

        public bool IsLarge { get { return false; } }

        /// <summary>
        /// This event does nothing, 
        /// </summary>
        public event PropertyEditedFn OnPropertyEdited { add { } remove { } }
    }

    /// <summary>
    /// Interaction logic for PropertyGridControl.xaml
    /// </summary>
    public partial class PropertyGridControl : UserControl
    {
        /// <summary>
        /// The root of the property tree.
        /// </summary>
        private RemotePropertyNode Root;

        /// <summary>
        /// We map from primitive types to type editors, which when
        /// instantiated will provide us with an ITypeEditor interface.
        /// </summary>
        private static Dictionary<Type, Type> DefaultEditors = new Dictionary<Type, Type>();

        private static Boolean PluginsLoaded = false;

        private PropertyGrid Grid;

        /// <summary>
        /// This maps names to whether or not those nodes were expanded. Technically it's not 100% correct
        /// since any name collisions would cause an expansion, but in general most names don't collide.
        /// </summary>
        private Dictionary<String, Boolean> IsNameExpanded = new Dictionary<String, Boolean>();

        private static void LoadAllPlugins()
        {
            if (PluginsLoaded == false)
            {
                PluginsLoaded = true;

                // Loop through all loaded assemblies
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    foreach (var type in assembly.GetTypes())
                    {
                        // If the type is a type editor
                        if (type.InheritsFrom(typeof(ITypeEditor)))
                        {
                            var attributes = type.GetCustomAttributes(typeof(DefaultEditorForTypeAttribute), true);

                            foreach (DefaultEditorForTypeAttribute defaultEditorForType in attributes)
                            {
                                DefaultEditors.Add(defaultEditorForType.Type, type);
                            }
                        }
                    }
                }
            }
        }

        public PropertyGridControl(PropertyGrid grid)
        {
            LoadAllPlugins();
            InitializeComponent();
            this.Grid = grid;
        }

        /// <summary>
        /// Sets the root of the property tree, which actually builds the UI for the tree.
        /// </summary>
        /// <param name="root">The root node of the property tree.</param>
        public void SetRoot(RemotePropertyNode root)
        {
            // Store the root so that we can update valeus later
            this.Root = root;

            // Clear all the child UI elements
            this.MainFrame.Children.Clear();

            // Now recursively create the UI
            this.CreateUi(this.MainFrame, root);
        }

        /// <summary>
        /// Update the tree to reflect any new property values (will not interrupt editing).
        /// </summary>
        public void UpdateValues()
        {
            // Recrusively update the tree starting at the root
            this.UpdateValues(this.Root);
        }


        private void UpdateValues(RemotePropertyNode node)
        {
            // Loop through all the children
            foreach (var child in node.Children)
            {
                // All primitive child nodes types must have editors
                if (child.Kind == NodeKind.Primitive || child.Kind == NodeKind.Enumerable)
                {
                    // As long as it's not currently being edited
                    if (child.TypeEditor.IsEditing == false)
                    {
                        // Set the value on the type editor
                        child.TypeEditor.SetValue(child.CurrentValue);
                    }
                }
                else if (child.Kind == NodeKind.Expandable)
                {
                    // Update recurse into child nodes if this is an expandable node
                    UpdateValues(child);
                }
            }
        }

        private static void AddAllChildren(List<UIElement> children, StackPanel parent)
        {
            foreach (var child in children)
            {
                parent.Children.Add(child);
            }
        }

        private void CreateExpander(String name, out UIElement expander, out StackPanel panel, Boolean defaultExpanded)
        {
            // Create the expander
            var expand = new Expander();
            panel = new StackPanel();

            // Give the panel some margines
            panel.Margin = new Thickness(10, 0, 0, 0);
            panel.SnapsToDevicePixels = true;

            // Setup the expander (including adding the panel as a child)
            expand.Content = panel;
            expand.Header = name;
            expand.SnapsToDevicePixels = true;

            // Add a margin to the expander to keep things clean
            expand.Margin = new Thickness(0, 0, 0, 4);

            // Create a border around the expander
            var outerLine = new Border();
            outerLine.BorderThickness = new Thickness(0, 1, 0, 0);
            outerLine.BorderBrush = Brushes.Black;
            outerLine.CornerRadius = new CornerRadius(0);
            outerLine.SnapsToDevicePixels = true;
            outerLine.Child = expand;

            expander = outerLine;
            expand.IsExpanded = defaultExpanded;

            bool userExpanded;
            if (this.IsNameExpanded.TryGetValue(name, out userExpanded))
            {
                expand.IsExpanded = userExpanded;
            }

            expand.Expanded += (sender, e) =>
            {
                this.IsNameExpanded[name] = true;
                e.Handled = true;
            };
            expand.Collapsed += (sender, e) =>
            {
                this.IsNameExpanded[name] = false;
                e.Handled = true;
            };
        }

        private void CreateUi(StackPanel parentUi, RemotePropertyNode node)
        {
            // Assume we have no categories
            Boolean hasCategories = false;

            // Create a dictionary for if we encounter any categories,
            // we can link up the category name with all child elements
            var categoryUi = new Dictionary<String, List<UIElement>>();

            // Loop through all the children
            foreach (var child in node.Children)
            {
                // The default category is the 'Misc' category
                // (where we put everything that isn't categorized)
                String childCategory = "Misc";

                // If the child has a category...
                if (child.OptionalCategory != null)
                {
                    // We have at least one node using categories!
                    hasCategories = true;

                    // Set the current category to the child's category
                    childCategory = child.OptionalCategory;
                }

                // An optional list of elements for this specific category
                List<UIElement> categoryCollection;

                // If we've just started this category..
                if (categoryUi.TryGetValue(childCategory, out categoryCollection) == false)
                {
                    // Create that new list of categories
                    categoryCollection = new List<UIElement>();

                    // Add it to the map we created above
                    categoryUi.Add(childCategory, categoryCollection);
                }

                // If the type is expandable...
                if (child.Kind == NodeKind.Expandable)
                {
                    // Create an expander and a stack panel inside the expander
                    UIElement expander;
                    StackPanel panel;

                    // Create the expander and stack panel
                    CreateExpander(child.OptionalName ?? String.Empty, out expander, out panel, child.DefaultExpanded);

                    // Finally, add this to the list under the current category
                    categoryCollection.Add(expander);

                    // Create the ui recursively for this child node
                    this.CreateUi(panel, child);
                }
                // If the child is a primitive that should be able to be edited by a type editor...
                else if (child.Kind == NodeKind.Primitive || child.Kind == NodeKind.Enumerable)
                {
                    // Create a dock-panel that both the property and its label go inside
                    var panel = new DockPanel();
                    panel.HorizontalAlignment = HorizontalAlignment.Stretch;
                    panel.SnapsToDevicePixels = true;

                    // If we're a primitive, we need to lookup our type editor
                    if (child.Kind == NodeKind.Primitive)
                    {
                        // Attempt to get the type editor using the child's type
                        Type typeEditorType;
                        if (DefaultEditors.TryGetValue(child.Type, out typeEditorType) == false)
                        {
                            // If we did not find it, then we set it to the unknown editor
                            // The unknown editor just displays the value as readonly text
                            typeEditorType = typeof(UnknownTypeEditor);
                        }

                        // Create whatever type editor we decided to use (pass in the node's type)
                        // Some type editors can share quite a few types, so it must know the node's type
                        child.TypeEditor = (ITypeEditor)Activator.CreateInstance(typeEditorType, child.Type);
                    }
                    else
                    {
                        // Otherwise, if we're enumerable, we just instantiate the editor ourselves
                        child.TypeEditor = new EnumerableEditor(child.EnumeratedOptions);
                    }

                    // Attempt to snap the type editor for visual effect
                    child.TypeEditor.Content.SnapsToDevicePixels = true;

                    // If the child has a name...
                    if (child.OptionalName != null)
                    {
                        // Create a label to represent the text
                        var properyLabel = new TextBlock();
                        properyLabel.Text = child.OptionalName;
                        panel.Children.Add(properyLabel);

                        // If the type editor is large...
                        if (child.TypeEditor.IsLarge)
                        {
                            // Dock the label on top so that the editor goes beneith it
                            DockPanel.SetDock(properyLabel, Dock.Top);
                        }
                        else
                        {
                            // We don't want to fill the area inbetween
                            //panel.LastChildFill = false;

                            // Otherwise, dock the label on the left, and the editor goes to the right
                            DockPanel.SetDock(properyLabel, Dock.Left);
                            DockPanel.SetDock(child.TypeEditor.Content, Dock.Right);

                            // Don't let the type editor get smaller than this size
                            child.TypeEditor.Content.HorizontalAlignment = HorizontalAlignment.Right;
                            child.TypeEditor.Content.MinWidth = 80;
                        }
                    }

                    // Add the type editor to the dock panel (may go to the right, or below)
                    panel.Children.Add(child.TypeEditor.Content);


                    var localChild = child;
                    child.TypeEditor.OnPropertyEdited += (value) =>
                    {
                        localChild.CurrentValue = value;
                        this.Grid.SetRemoteValue(localChild, value);
                    };

                    // Create a small border around the type editors
                    var border = new Border();
                    border.Child = panel;
                    border.BorderThickness = new Thickness(0, 0, 0, 1);
                    border.BorderBrush = Brushes.LightGray;
                    border.Margin = new Thickness(0, 5, 0, 0);
                    border.Padding = new Thickness(0, 0, 0, 5);
                    border.SnapsToDevicePixels = true;

                    categoryCollection.Add(border);
                }
            }

            if (hasCategories)
            {
                foreach (var category in categoryUi)
                {
                    // Create an expander and a stack panel inside the expander
                    UIElement expander;
                    StackPanel panel;

                    // Create the expander and stack panel
                    CreateExpander(category.Key, out expander, out panel, false);

                    AddAllChildren(category.Value, panel);

                    parentUi.Children.Add(expander);
                }
            }
            else
            {
                foreach (var category in categoryUi)
                {
                    AddAllChildren(category.Value, parentUi);
                }
            }
        }
    }
}
